package com.wao.base.domain;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.apache.commons.lang3.StringUtils;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

@SuppressWarnings({"unchecked", "rawtypes"})
@MappedSuperclass
public abstract class BaseTreeableEntity<T extends BaseTreeableEntity<T>> extends BaseMultitenantEntity {

    private static final long serialVersionUID = 1L;

    @Column(name = "name", nullable = false)
    protected String name;

    @Column(name = "full_id")
    protected String fullId;

    @Column(name = "jhi_level")
    protected Integer level;

    @Column(name = "display_order")
    protected Integer displayOrder;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    protected T parent;

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, mappedBy = "parent")
    @OrderBy("display_order, name")
    protected Collection<T> children = new HashSet<>(0);

    @JsonIgnore
    public String getFullId() {
        return fullId;
    }

    public void setFullId(String fullId) {
        this.fullId = fullId;
    }

    public Integer getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getFullname() {
        return getFullname(",", null);
    }

    public String getFullname(Long tree) {
        return getFullname(",", tree);
    }

    public String getFullname(String seperator) {
        return getFullname(seperator, null);
    }

    public String getFullname(String seperator, Long tree) {
        if (name == null)
            return null;
        StringBuilder fullname = new StringBuilder(name);
        BaseTreeableEntity e = this;
        while ((e = e.getParent()) != null) {
            if (!(e.isRoot() && StringUtils.isBlank(e.getName())))
                fullname.insert(0, e.getName() + seperator);
            if (tree != null && tree.equals(e.getId()))
                break;
        }
        return fullname.toString();
    }

    @JsonIgnore
    public Integer getDisplayOrder() {
        return displayOrder;
    }

    public void setDisplayOrder(int displayOrder) {
        this.displayOrder = displayOrder;
    }

    @JsonIgnore
    public Collection<T> getChildren() {
        return children;
    }

    public void setChildren(Collection<T> children) {
        this.children = children;
    }

    @JsonIgnore
    public boolean isLeaf() {
        return this.children == null || this.children.size() == 0;
    }

    public boolean isHasChildren() {
        return !isLeaf();
    }

    @JsonIgnore
    public boolean isRoot() {
        return this.parent == null;
    }

    @JsonIgnore
    public T getParent() {
        return parent;
    }

    public void setParent(T parent) {
        this.parent = parent;
    }

    public T getDescendantOrSelfById(Long id) {
        if (id == null)
            throw new IllegalArgumentException("id must not be null");
        if (id.equals(this.getId()))
            return (T) this;
        for (T t : getChildren()) {
            if (id.equals(t.getId())) {
                return t;
            } else {
                T tt = t.getDescendantOrSelfById(id);
                if (tt != null)
                    return tt;
            }
        }
        return null;
    }

    public T getDescendantOrSelfByName(String name) {
        if (name == null)
            throw new IllegalArgumentException("name must not be null");
        if (name.equals(this.getName()))
            return (T) this;
        for (T t : getChildren()) {
            if (name.equals(t.getName())) {
                return t;
            } else {
                T tt = t.getDescendantOrSelfByName(name);
                if (tt != null)
                    return tt;
            }
        }
        return null;
    }

    @JsonIgnore
    public List<T> getDescendants() {
        List<T> ids = new ArrayList<>();
        if (!this.isLeaf())
            for (Object obj : this.getChildren()) {
                collect((T) obj, ids);
            }
        return ids;
    }

    @JsonIgnore
    public List<T> getDescendantsAndSelf() {
        List<T> ids = new ArrayList<>();
        collect((T) this, ids);
        return ids;
    }

    private void collect(T node, Collection<T> coll) {
        coll.add(node);
        if (node.isLeaf())
            return;
        for (Object obj : node.getChildren()) {
            collect((T) obj, coll);
        }
    }

    public boolean isAncestorOrSelfOf(T t) {
        T parent = t;
        while (parent != null) {
            if (parent.getId().equals(this.getId()))
                return true;
            parent = parent.getParent();
        }
        return false;
    }

    public boolean isDescendantOrSelfOf(T t) {
        return t != null && t.isAncestorOrSelfOf((T) this);
    }

    public T getAncestor(int level) {
        if (level < 1 || level > this.getLevel())
            return null;
        T parent = (T) this;
        while (parent != null) {
            if (parent.getLevel() == level)
                return parent;
            parent = parent.getParent();
        }
        return null;
    }

}
